﻿using System;
using System.Text;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Threading;

namespace XiaoSe.Web
{
    public class HttpRequest
    {
        #region 私有字段

        /// <summary>
        /// 发送请求Socket
        /// </summary>
        private Socket _socket;

        /// <summary>
        /// 接收数据时间间隔
        /// </summary>
        private readonly int _recvDataSpan = 100;

        /// <summary>
        /// 发送数据编码
        /// </summary>
        private Encoding _sendEncode = Encoding.UTF8;

        /// <summary>
        /// 发送数据编码
        /// </summary>
        public Encoding SendEncode
        {
            set { _sendEncode = value; }
        }

        /// <summary>
        /// Post请求方式请求体
        /// </summary>
        private StringBuilder _sbPostBody;

        /// <summary>
        /// 是否要上传文件
        /// </summary>
        private bool _isHaveFile;

        /// <summary>
        /// Post请求方式请求体大小
        /// </summary>
        private int _contentSize;

        /// <summary>
        /// 文件字节数据占位符
        /// </summary>
        private readonly string _fileBytePlaceHolder = Guid.NewGuid().ToString();

        #endregion

        #region 远程主机信息

        private readonly string _requestPath;

        /// <summary>
        /// 远程主机名
        /// </summary>
        private string _host;

        /// <summary>
        /// 远程端口号
        /// </summary>
        private int _port;

        /// <summary>
        /// 远程IP
        /// </summary>
        private IPAddress[] _ips;

        private IDictionary<string, string> _requestHeader;

        /// <summary>
        /// 请求头
        /// </summary>
        public IDictionary<string, string> RequestHeader
        {
            set { _requestHeader = value; }
        }

        private IDictionary<string, string> _postTextValue;

        /// <summary>
        /// POST提交时，要提交的文本值
        /// </summary>
        public IDictionary<string, string> PostTextValue
        {
            set { _postTextValue = value; }
        }

        private IList<UploadFile> _uploadFile = new List<UploadFile>();

        /// <summary>
        /// POST提交时，要上传的文件
        /// </summary>
        public IList<UploadFile> UploadFile
        {
            set { _uploadFile = value; }
        }

        private string _httpMethod = "GET";

        /// <summary>
        /// http请求方式
        /// </summary>
        public string HttpMethod
        {
            set
            {
                if (!"post".Equals(value.ToLower()) && !"get".Equals(value.ToLower()))
                {
                    throw new Exception("HttpMethod 只能为POSET或者GET");
                }
                _httpMethod = "post".Equals(value.ToLower()) ? "POST" : "GET";
            }
        }

        #endregion

        #region 构造函数

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestPath">请求地址，例如 http://www.xxx.com:8080/index.html ，如果是80端口可以不输入端口号</param>
        public HttpRequest(string requestPath)
        {
            _requestPath = requestPath;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestPath">请求地址，例如 http://www.xxx.com:8080/index.html ，如果是80端口可以不输入端口号</param> 
        /// <param name="recvDataSpan">接收数据时间间隔，网速慢的可以设置时间间隔大一点(以毫秒为单位)</param>
        public HttpRequest(string requestPath, int recvDataSpan)
            : this(requestPath)
        {
            _recvDataSpan = recvDataSpan;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestPath">请求地址，例如 http://www.xxx.com:8080/index.html ，如果是80端口可以不输入端口号</param> 
        /// <param name="recvDataSpan">接收数据时间间隔，网速慢的可以设置时间间隔大一点(以毫秒为单位)</param>
        /// <param name="requestHeader">请求头</param>
        public HttpRequest(string requestPath, int recvDataSpan, IDictionary<string, string> requestHeader)
            : this(requestPath, recvDataSpan)
        {
            _requestHeader = requestHeader;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestPath">请求地址，例如 http://www.xxx.com:8080/index.html ，如果是80端口可以不输入端口号</param> 
        /// <param name="recvDataSpan">接收数据时间间隔，网速慢的可以设置时间间隔大一点(以毫秒为单位)</param>
        /// <param name="requestHeader">请求头</param>
        /// <param name="postTextValue">POST提交时，要提交的文本值</param>
        public HttpRequest(string requestPath, int recvDataSpan, IDictionary<string, string> requestHeader, IDictionary<string, string> postTextValue)
            : this(requestPath, recvDataSpan, requestHeader)
        {
            _postTextValue = postTextValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestPath">请求地址，例如 http://www.xxx.com:8080/index.html ，如果是80端口可以不输入端口号</param> 
        /// <param name="recvDataSpan">接收数据时间间隔，网速慢的可以设置时间间隔大一点(以毫秒为单位)</param>
        /// <param name="requestHeader">请求头</param>
        /// <param name="postTextValue">POST提交时，要提交的文本值</param>
        /// <param name="uploadFile">POST提交时，要上传的文件</param>
        public HttpRequest(string requestPath, int recvDataSpan, IDictionary<string, string> requestHeader, IDictionary<string, string> postTextValue, IList<UploadFile> uploadFile)
            : this(requestPath, recvDataSpan, requestHeader, postTextValue)
        {
            _uploadFile = uploadFile;
        }

        #endregion

        /// <summary>
        /// 根据地址路径获取远程主机信息
        /// </summary>
        /// <returns></returns>
        private bool GetRemoteInfo()
        {
            Uri uri;
            if (!Uri.TryCreate(_requestPath, UriKind.RelativeOrAbsolute, out uri))
            {
                return false;
            }
            //获取到远程主机名跟端口号
            _host = uri.Host;
            _port = uri.Port;
            //获取到远程IP地址
            try
            {
                _ips = Dns.GetHostAddresses(_host);
            }
            catch
            {
                return false;
            }
            return _ips != null && _ips.Length > 0;
        }

        /// <summary>
        /// 生成请求头字符串
        /// </summary>
        /// <returns></returns>
        private string CreateRequestHeader()
        {
            StringBuilder sb = new StringBuilder();
            if (_httpMethod == "GET")
            {
                sb.Append("GET " + _requestPath + " HTTP/1.1\r\n");
            }
            else
            {
                sb.Append("POST " + _requestPath + " HTTP/1.1\r\n");
            }
            sb.Append("Host: " + _host + ":" + _port + "\r\n");

            if (_requestHeader != null && _requestHeader.Count > 0)
            {
                foreach (KeyValuePair<string, string> kv in _requestHeader)
                {
                    sb.Append(kv.Key + ": " + kv.Value + "\r\n");
                }
            }
            if (_httpMethod == "POST")
            {
                if (_uploadFile == null || _uploadFile.Count <= 0)
                {
                    _isHaveFile = false;
                }
                else
                {
                    _isHaveFile = true;
                    foreach (UploadFile file in _uploadFile)
                    {
                        if (!File.Exists(file.FilePath))
                        {
                            throw new Exception("文件:" + file.FilePath + " 不存在");
                        }
                    }
                }
                _sbPostBody = new StringBuilder();
                if (_isHaveFile)//如果需要上传文件
                {
                    string strFlag = "---------------------------" + DateTime.Now.ToFileTimeUtc();
                    sb.Append("Content-Type: multipart/form-data; boundary=" + strFlag + "\r\n");
                    //拼接文本值
                    if (_postTextValue != null && _postTextValue.Count > 0)
                    {
                        foreach (KeyValuePair<string, string> kv in _postTextValue)
                        {
                            _sbPostBody.Append("--" + strFlag + "\r\n");
                            _sbPostBody.Append("Content-Disposition: form-data; name=\"" + kv.Key + "\"\r\n\r\n");
                            _sbPostBody.Append(kv.Value + "\r\n");
                        }
                    }
                    //拼接上传文件
                    if (_uploadFile != null)
                    {
                        foreach (UploadFile file in _uploadFile)
                        {
                            FileInfo fileInfo = new FileInfo(file.FilePath);
                            _sbPostBody.Append("--" + strFlag + "\r\n");
                            _sbPostBody.Append("Content-Disposition: form-data; name=\"" + file.Name + "\"; filename=\"" + fileInfo.Name + "\"\r\n");
                            if (string.IsNullOrEmpty(file.ContentType))
                            {
                                _sbPostBody.Append("Content-Type: application/x-msdownload\r\n\r\n" + _fileBytePlaceHolder + "\r\n");
                            }
                            else
                            {
                                _sbPostBody.Append("Content-Type: " + file.ContentType + "\r\n\r\n" + _fileBytePlaceHolder + "\r\n");
                            }
                            _contentSize += (int)fileInfo.Length;
                        }
                        _sbPostBody.Append("--" + strFlag + "--");
                        _contentSize += _sendEncode.GetBytes(_sbPostBody.ToString()).Length - (_sendEncode.GetBytes("" + _fileBytePlaceHolder + "").Length * _uploadFile.Count);
                    }
                }
                else
                {
                    sb.Append("Content-Type: application/x-www-form-urlencoded\r\n");

                    if (_postTextValue != null && _postTextValue.Count > 0)
                    {
                        foreach (KeyValuePair<string, string> kv in _postTextValue)
                        {
                            _sbPostBody.Append(kv.Key + "=" + kv.Value + "&");
                        }
                        _sbPostBody.Remove(_sbPostBody.Length - 1, 1);
                        _contentSize = _sendEncode.GetBytes(_sbPostBody.ToString()).Length;
                    }
                    else
                    {
                        _contentSize = 0;
                    }
                }
                sb.Append("Content-Length: " + _contentSize + "\r\n");
            }
            sb.Append("\r\n");
            return sb.ToString();
        }

        /// <summary>
        /// 初始化Socket
        /// </summary>
        private bool InitSocket()
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                _socket.Connect(_ips, _port);
                return true;
            }
            catch
            {
                Dispose();
                return false;
            }
        }

        /// <summary>
        /// 发送数据到远程主机
        /// </summary>
        /// <returns></returns>
        private bool SendDataToRemote()
        {
            if (!GetRemoteInfo())
            {
                return false;
            }
            string strHeader = CreateRequestHeader();
            if (!InitSocket())
            {
                return false;
            }
            _socket.Send(_sendEncode.GetBytes(strHeader));
            if (_httpMethod != "POST") return true;
            string strBody = _sbPostBody.ToString();
            byte[] send;
            if (_isHaveFile)
            {
                //按文件字节占位符分割
                string[] array = strBody.Split(new[] { "" + _fileBytePlaceHolder + "" }, StringSplitOptions.RemoveEmptyEntries);
                int i = 0;
                byte[] temp = new byte[4096];
                foreach (UploadFile file in _uploadFile)
                {
                    send = _sendEncode.GetBytes(array[i]);
                    _socket.Send(send);
                    FileStream fileStream = new FileStream(file.FilePath, FileMode.Open);
                    int lenght;
                    do
                    {
                        lenght = fileStream.Read(temp, 0, temp.Length);
                        _socket.Send(temp, 0, lenght, SocketFlags.None);
                    }
                    while (lenght >= 4096);
                    fileStream.Close();
                    i++;
                }
                send = _sendEncode.GetBytes(array[i]);
                _socket.Send(send);
            }
            else
            {
                if (_contentSize <= 0) return true;
                send = _sendEncode.GetBytes(strBody);
                _socket.Send(send);
            }
            return true;
        }

        /// <summary>
        /// 获取响应内容
        /// </summary>
        /// <returns></returns>
        public HttpResponse GetHttpResponse()
        {
            return !SendDataToRemote() ? null : RecvAllData();
        }

        /// <summary>
        /// 接收所有数据
        /// </summary>
        /// <returns></returns>
        private HttpResponse RecvAllData()
        {
            byte[] recv = new byte[10240];
            Thread.Sleep(_recvDataSpan);//睡眠一下，接收数据太快会有问题的
            int lenght = _socket.Receive(recv);
            HttpResponse response = new HttpResponse(_requestPath,recv, 0, lenght);
            while (lenght >= 10240 || _socket.Available > 0)
            {
                Thread.Sleep(_recvDataSpan);//睡眠一下，接收数据太快会有问题的
                lenght = _socket.Receive(recv);
                response.AppendData(recv, 0, lenght);
            }
            response.Dispose();
            return response;
        }


        /// <summary>
        /// 清理资源
        /// </summary>
        public void Dispose()
        {
            if (_socket == null) return;
            try
            {
                _socket.Shutdown(SocketShutdown.Both);
                _socket.Close();
            }
            catch
            {

            }
        }
    }

    public abstract class UploadFile
    {
        public string Name { get; private set; }
        public string FilePath { get; private set; }
        public string ContentType { get; private set; }
        protected UploadFile(string name, string filePath, string contentType)
        {
            Name = name;
            FilePath = filePath;
            ContentType = contentType;
        }
    }
}
